using System;
using System.Collections.Generic;
using System.Text;

namespace v6502
{
    class CPU
    {
        enum statusRegisters : int
        {
            carry             = 0,
            zero              = 1,
            interruptDisable  = 2,
            decimalMode       = 3,
            brk               = 4,
            overflow          = 6,
            negative          = 7
        };

        enum addressingModes : int
        {
            imp                 = 0,
            accumulator         = 1,
            immediate           = 2,
            zeropage            = 3,
            zeropagex           = 4,
            zeropagey           = 5,
            relative            = 6,
            absolute            = 7,
            absolutex           = 8,
            absolutey           = 9,
            indirect            = 10,
            indexedindirect     = 11,
            indirectindexed     = 12
        };

        private bool executing;
        private RAM memory;
        private int PC;    // Program Counter
        private int AC;    // Accumulator
        private int SP;    // Stack Pointer
        private int X;     // X Register
        private int Y;     // Y Register
        private bool[] statusRegister = new bool[8];
        private byte currentOpcode;
        private int stackStart;

        private byte[] stack = new byte[256];

        /*frmMain mainForm;
        public CPU(Form frm)
        {
            mainForm = (frmMain)frm;
        }*/

        public CPU(RAM ram)
        {
            memory = ram; //new RAM(0xFFFF);
            PC = 0x600;
            AC = 0x00;
            //SP = 0x100;
            X = 0x00;
            Y = 0x00;
            stackStart = 0x100;
            SP = stackStart;
            for (int i = 0; i < stack.Length; i++)
                stack[i] = 0x00;
            for (int i = 0; i < statusRegister.Length; i++)
                statusRegister[i] = false;
            executing = true;
        }

        private void push(byte value)
        {
            // check stack size...
            SP++;
            stack[SP - stackStart] = value;
        }

        private int pop()
        {
            // check stack size...
            int tmp = 0;
            tmp = stack[SP - stackStart];
            SP--;
            return tmp;
        }

        public void run()
        {
            //memory.write(0x0A,0x00);
            while (executing)
            {
                fetch();
                execute();
                PC++;
            }
        }

        private void fetch()
        {
            currentOpcode = memory.read(PC);
        }

        private void execute()
        {
            int tmp = 0;
            switch (currentOpcode)
            {
                //
                // ADC : Add With Carry
                //
                case 0x69:  // Addressing Mode : Immediates
                    {       // Length Byte(s) : 2
                        PC++;
                        tmp = getOperand((int)addressingModes.immediate);
                        if (statusRegister[(int)statusRegisters.carry] == true)
                            tmp += 1;
                        AC = AC + tmp;
                        setCarryFlag(AC);
                        setOverflowFlag(AC);
                        if (AC > 255)
                            AC = AC - 256;
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                case 0x65:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        tmp = getOperand((int)addressingModes.zeropage);//immediate);
                        if (statusRegister[(int)statusRegisters.carry] == true)
                            tmp += 1;
                        AC = AC + tmp;
                        setCarryFlag(AC);
                        setOverflowFlag(AC);
                        if (AC > 255)
                            AC = AC - 256;
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                case 0x75:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        PC++;
                        tmp = getOperand((int)addressingModes.zeropagex);//immediate);
                        if (statusRegister[(int)statusRegisters.carry] == true)
                            tmp += 1;
                        AC = AC + tmp;
                        setCarryFlag(AC);
                        setOverflowFlag(AC);
                        if (AC > 255)
                            AC = AC - 256;
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                case 0x6D:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        tmp = getOperand((int)addressingModes.absolute);//immediate);
                        if (statusRegister[(int)statusRegisters.carry] == true)
                            tmp += 1;
                        AC = AC + tmp;
                        setCarryFlag(AC);
                        setOverflowFlag(AC);
                        if (AC > 255)
                            AC = AC - 256;
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
						PC++; // 3 byte instruction
                        break;
                    }
                case 0x7D:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        PC++;
                        tmp = getOperand((int)addressingModes.absolutex);//immediate);
                        if (statusRegister[(int)statusRegisters.carry] == true)
                            tmp += 1;
                        AC = AC + tmp;
                        setCarryFlag(AC);
                        setOverflowFlag(AC);
                        if (AC > 255)
                            AC = AC - 256;
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
						PC++; // 3 byte instruction
                        break;
                    }
                case 0x79:  // Addressing Mode : Absolute, Y
                    {       // Length Byte(s) : 3
                        PC++;
                        tmp = getOperand((int)addressingModes.absolutey);//immediate);
                        if (statusRegister[(int)statusRegisters.carry] == true)
                            tmp += 1;
                        AC = AC + tmp;
                        setCarryFlag(AC);
                        setOverflowFlag(AC);
                        if (AC > 255)
                            AC = AC - 256;
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
						PC++; // 3 byte instruction
                        break;
                    }
                case 0x61:  // Addressing Mode : (Indirect, X)
                    {       // Length Byte(s) : 2
                        PC++;
                        tmp = getOperand((int)addressingModes.indexedindirect);//immediate);
                        if (statusRegister[(int)statusRegisters.carry] == true)
                            tmp += 1;
                        AC = AC + tmp;
                        setCarryFlag(AC);
                        setOverflowFlag(AC);
                        if (AC > 255)
                            AC = AC - 256;
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                case 0x71:  // Addressing Mode : (Indirect), Y
                    {       // Length Byte(s) : 2
                        PC++;
                        tmp = getOperand((int)addressingModes.indirectindexed);//immediate);
                        if (statusRegister[(int)statusRegisters.carry] == true)
                            tmp += 1;
                        AC = AC + tmp;
                        setCarryFlag(AC);
                        setOverflowFlag(AC);
                        if (AC > 255)
                            AC = AC - 256;
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                //
                // AND : Logical AND
                //
                case 0x29:  // Addressing Mode : Immediate 
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = AC & getOperand((int)addressingModes.immediate);
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                case 0x25:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = AC & getOperand((int)addressingModes.zeropage);
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                case 0x35:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = AC & getOperand((int)addressingModes.zeropagex);
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                case 0x2D:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        AC = AC & getOperand((int)addressingModes.absolute);
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
						PC++; // 3 byte instruction
                        break;
                    }
                case 0x3D:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        PC++;
                        AC = AC & getOperand((int)addressingModes.absolutex);
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
						PC++; // 3 byte instruction
                        break;
                    }
                case 0x39:  // Addressing Mode : Absolute, Y
                    {       // Length Byte(s) : 3
                        PC++;
                        AC = AC & getOperand((int)addressingModes.absolutey);
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
						PC++; // 3 byte instruction
                        break;
                    }
                case 0x21:  // Addressing Mode : (Indirect, X)
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = AC & getOperand((int)addressingModes.indexedindirect);
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                case 0x31:  // Addressing Mode : (Indirect), Y
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = AC & getOperand((int)addressingModes.indirectindexed);
                        setZeroFlag(AC);
                        setNegativeFlag(AC);
                        break;
                    }
                //
                // ASL : Arethmetic Shift Left
                //
                case 0x0A:  // Addressing Mode : Accumulator 
                    {       // Length Byte(s) : 1
                        //AC = AC
                        break;
                    }
                case 0x06:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x16:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x0E:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x1E:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        break;
                    }
                //
                // BCC : Branch if Carry Clear
                //
                case 0x90:  // Addressing Mode: Relative
                    {       // Length Byte(s) : 2
                        int branchValue = getOperand((int)addressingModes.immediate);
                        if (statusRegister[(int)statusRegisters.carry] == false)
                        {
                            if (branchValue < 128)
                                PC += branchValue; // Forward
                            else
                                PC -= 256 - branchValue; // Backward
                        }
                        break;
                    }
                //
                // BCS : Branch if Carry Set
                //
                case 0xB0:  // Addressing Mode: Relative
                    {       // Length Byte(s) : 2
                        int branchValue = getOperand((int)addressingModes.immediate);
                        if (statusRegister[(int)statusRegisters.carry] == true)
                        {
                            if (branchValue < 128)
                                PC += branchValue; // Forward
                            else
                                PC -= 256 - branchValue; // Backward
                        }
                        break;
                    }
                //
                // BEQ : Branch if Equal
                //
                case 0xF0:  // Addressing Mode: Relative
                    {       // Length Byte(s) : 2
                        int branchValue = getOperand((int)addressingModes.immediate);
                        if (statusRegister[(int)statusRegisters.zero] == true)
                        {
                            if (branchValue < 128)
                                PC += branchValue; // Forward
                            else
                                PC -= 256 - branchValue; // Backward
                        }
                        break;
                    }
                //
                // BIT : Bit Test
                //
                case 0x24:  // Addressing Mode: Zero Page
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x2C:  // Addressing Mode: Absolute
                    {       // Length Byte(s) : 3
                        break;
                    }
                //
                // BMI : Branch if Minus
                //
                case 0x30:  // Addressing Mode: Relative
                    {       // Length Byte(s) : 2
                        int branchValue = getOperand((int)addressingModes.immediate);
                        if (statusRegister[(int)statusRegisters.negative] == true)
                        {
                            if (branchValue < 128)
                                PC += branchValue; // Forward
                            else
                                PC -= 256 - branchValue; // Backward
                        }
                        break;
                    }
                //
                // BNE : Branch if Not Equal
                //
                case 0xD0:  // Addressing Mode: Relative
                    {       // Length Byte(s) : 2
                        int branchValue = getOperand((int)addressingModes.immediate);
                        if (statusRegister[(int)statusRegisters.zero] == false)
                        {
                            if (branchValue < 128)
                                PC += branchValue; // Forward
                            else
                                PC -= 256 - branchValue; // Backward
                        }
                        break;
                    }
                //
                // BPL : Branch if Positive
                //
                case 0x10:  // Addressing Mode: Relative
                    {       // Length Byte(s) : 2
                        int branchValue = getOperand((int)addressingModes.immediate);
                        if (statusRegister[(int)statusRegisters.negative] == false)
                        {
                            if (branchValue < 128)
                                PC += branchValue; // Forward
                            else
                                PC -= 256 - branchValue; // Backward
                        }
                        break;
                    }
                //
                // BRK : Force Interupt
                //
                case 0x00:  // Addressing Mode : Implied
                    {       // Length Byte(s) : 1
                        executing = false; // ?
                        break;
                    }
                //
                // BVC : Branch if Overflow Clear
                //
                case 0x50:  // Addressing Mode : Relative
                    {       // Length Byte(s) : 2
                        int branchValue = getOperand((int)addressingModes.immediate);
                        if (statusRegister[(int)statusRegisters.overflow] == false)
                        {
                            if (branchValue < 128)
                                PC += branchValue; // Forward
                            else
                                PC -= 256 - branchValue; // Backward
                        }
                        break;
                    }
                //
                // BVS : Branch if Overflow Set
                //
                case 0x70:  // Addressing Mode : Relative
                    {       // Length Byte(s) : 2
                        int branchValue = getOperand((int)addressingModes.immediate);
                        if (statusRegister[(int)statusRegisters.overflow] == true)
                        {
                            if (branchValue < 128)
                                PC += branchValue; // Forward
                            else
                                PC -= 256 - branchValue; // Backward
                        }
                        break;
                    }
                //
                // CLC : Clear Carry Flag
                //
                case 0x18:  // Addressing Mode : Implied
                    {       // Length Byte(s) : 1
                        // PC++;
                        statusRegister[(int)statusRegisters.carry] = false;
                        break;
                    }
                //
                // CLD : Clear Decimal Mode
                //
                case 0xD8:  // Addressing Mode : Implied
                    {       // Length Byte(s) : 1
                        // PC++;
                        statusRegister[(int)statusRegisters.decimalMode] = false;
                        break;
                    }
                //
                // CLI : Clear Interupt Disable
                //
                case 0x58:  // Addressing Mode : Implied
                    {       // Length Byte(s) : 1
                        // PC++;
                        statusRegister[(int)statusRegisters.interruptDisable] = false;
                        break;
                    }
                //
                // CLV : Clear Overflow Flag
                //
                case 0xB8:  // Addressing Mode : Implied
                    {       // Length Byte(s) : 1
                        // PC++;
                        statusRegister[(int)statusRegisters.overflow] = false;
                        break;
                    }
                //
                // CMP : Compare
                //
                case 0xC9:  // Addressing Mode : Immediate
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0xC5:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0xD5:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0xCD:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0xDD:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0xD9:  // Addressing Mode : Absolute, Y
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0xC1:  // Addressing Mode : (Indirect, X)
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0xD1:  // Addressing Mode : (Indirect), Y
                    {       // Length Byte(s) : 2
                        break;
                    }
                //
                // CPX : Compare X Register
                //
                case 0xE0:  // Addressing Mode : Immediate
                    {       // Length Byte(s) : 2
                        PC++;
                        int value = getOperand((int)addressingModes.immediate);
                        if (X >= value)
                            statusRegister[(int)statusRegisters.carry] = true;
                        if (X == value)
                            statusRegister[(int)statusRegisters.zero] = true;

                        int result = X - value;     // bit 7 aparently...
                        if (result < 0)
                            result += 256;
                        setNegativeFlag(result);
                        break;
                    }
                case 0xE4:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        int address = getOperand((int)addressingModes.immediate);
                        int value = memory.read(address);
                        if (X >= value)
                            statusRegister[(int)statusRegisters.carry] = true;
                        if (X == value)
                            statusRegister[(int)statusRegisters.zero] = true;

                        int result = X - value;     // bit 7 aparently...
                        if (result < 0)
                            result += 256;
                        setNegativeFlag(result);
                        break;
                    }
                case 0xEC:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        int address = getOperand((int)addressingModes.absolute);
                        int value = memory.read(address);
                        if (X >= value)
                            statusRegister[(int)statusRegisters.carry] = true;
                        if (X == value)
                            statusRegister[(int)statusRegisters.zero] = true;

                        int result = X - value;     // bit 7 aparently...
                        if (result < 0)
                            result += 256;
                        setNegativeFlag(result);
						PC++; // 3 byte instruction
                        break;
                    }
                //
                // CPY : Compare Y Register
                //
                case 0xC0:  // Addressing Mode : Immediate
                    {       // Length Byte(s) : 2
                        PC++;
                        int value = getOperand((int)addressingModes.immediate);
                        if (Y >= value)
                            statusRegister[(int)statusRegisters.carry] = true;
                        if (Y == value)
                            statusRegister[(int)statusRegisters.zero] = true;

                        int result = Y - value;     // bit 7 aparently...
                        if (result < 0)
                            result += 256;
                        setNegativeFlag(result);
                        break;
                    }
                case 0xC4:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        int address = getOperand((int)addressingModes.zeropage);
                        int value = memory.read(address);
                        if (Y >= value)
                            statusRegister[(int)statusRegisters.carry] = true;
                        if (Y == value)
                            statusRegister[(int)statusRegisters.zero] = true;

                        int result = Y - value;     // bit 7 aparently...
                        if (result < 0)
                            result += 256;
                        setNegativeFlag(result);
                        break;
                    }
                case 0xCC:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        int address = getOperand((int)addressingModes.absolute);
                        int value = memory.read(address);
                        if (Y >= value)
                            statusRegister[(int)statusRegisters.carry] = true;
                        if (Y == value)
                            statusRegister[(int)statusRegisters.zero] = true;

                        int result = Y - value;     // bit 7 aparently...
                        if (result < 0)
                            result += 256;
                        setNegativeFlag(result);
						PC++; // 3 byte instruction
                        break;
                    }
                //
                // DEC : Decrement Memory
                //
                case 0xC6:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        //int address = memory.read(PC);
                        //int value = memory.read(address);
                        int address = getOperand((int)addressingModes.zeropage);
                        int value = memory.read(address);
                        value--;
                        memory.write(address, (byte)value);
                        setZeroFlag(value);
                        setNegativeFlag(value);
                        break;
                    }
                case 0xD6:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        PC++;
                        int address = getOperand((int)addressingModes.zeropagex);
                        int value = memory.read(address);
                        value--;
                        memory.write(address, (byte)value);
                        setZeroFlag(value);
                        setNegativeFlag(value);
                        break;
                    }
                case 0xCE:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        //int address = memory.read(PC);
                        //PC++;
                        //address += 256 * memory.read(PC);
                        int address = getOperand((int)addressingModes.absolute);
                        int value = memory.read(address);
                        value--;
                        memory.write(address, (byte)value);
                        setZeroFlag(value);
                        setNegativeFlag(value);
						PC++; // 3 byte instruction
                        break;
                    }
                case 0xDE:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        PC++;
                        int address = getOperand((int)addressingModes.absolutex);
                        int value = memory.read(address);
                        value--;
                        memory.write(address, (byte)value);
                        setZeroFlag(value);
                        setNegativeFlag(value);
						PC++; // 3 byte instruction
                        break;
                    }
                //
                // DEX : Decrement X Register
                //
                case 0xCA:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        // PC++;
                        X--;
                        setNegativeFlag(X);
                        setZeroFlag(X);
                        break;
                    }
                //
                // DEY : Decrement Y Register
                //
                case 0x88:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        // PC++;
                        Y--;
                        setNegativeFlag(Y);
                        setZeroFlag(Y);
                        break;
                    }
                //
                // EOR : Exclusive Or
                //
                case 0x49:  // Addressing Mode : Immediate
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x45:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x55:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x4D:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x5D:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x59:  // Addressing Mode : Absolute, Y
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x41:  // Addressing Mode : (Indirect, X)
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x51:  // Addressing Mode : (Indirect), Y
                    {       // Length Byte(s) : 2
                        break;
                    }
                //
                // INC : Increment Memory
                //
                case 0xE6:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        int address = getOperand((int)addressingModes.zeropage);
                        int value = memory.read(address);
                        value++;
                        memory.write(address, (byte)value);
                        setZeroFlag(value);
                        setNegativeFlag(value);
                        break;
                    }
                case 0xF6:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        PC++;
                        int address = getOperand((int)addressingModes.zeropagex);
                        int value = memory.read(address);
                        value++;
                        memory.write(address, (byte)value);
                        setZeroFlag(value);
                        setNegativeFlag(value);
                        break;
                    }
                case 0xEE:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        int address = getOperand((int)addressingModes.absolute);
                        int value = memory.read(address);
                        value++;
                        memory.write(address, (byte)value);
                        setZeroFlag(value);
                        setNegativeFlag(value);
						PC++; // 3 byte instruction
                        break;
                    }
                case 0xFE:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        PC++;
                        int address = getOperand((int)addressingModes.absolutex);
                        int value = memory.read(address);
                        value++;
                        memory.write(address, (byte)value);
                        setZeroFlag(value);
                        setNegativeFlag(value);
						PC++; // 3 byte instruction
                        break;
                    }
                //
                // INX : Increment X Register
                //
                case 0xE8:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        //PC++;
                        X++;
                        setNegativeFlag(X);
                        setZeroFlag(X);
                        break;
                    }
                //
                // INY : Increment Y Register
                //
                case 0xC8:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        //PC++;
                        Y++;
                        setNegativeFlag(Y);
                        setZeroFlag(Y);
                        break;
                    }
                //
                // JMP : Jump
                //
                case 0x4C:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        PC = memory.read(getOperand((int)addressingModes.absolute));
                        break;
                    }
                case 0x6C:  // Addressing Mode : Indirect
                    {       // Length Byte(s) : 3
                        PC++;
                        PC = memory.read(getOperand((int)addressingModes.indirect));
                        break;
                    }
                //
                // JSR : Jump to Subroutine
                //
                case 0x20:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
						PC++;
						PC++;
						push((byte)PC);
						PC = memory.read(getOperand((int)addressingModes.absolute));
                        break;
                    }
                //
                // LDA : Load Accumulator
                //
                case 0xA9:  // Addressing Mode : Immediate
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = getOperand((int)addressingModes.immediate);
                        break;
                    }
                case 0xA5:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = memory.read(getOperand((int)addressingModes.zeropage));
                        break;
                    }
                case 0xB5:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = memory.read(getOperand((int)addressingModes.zeropagex));
                        break;
                    }
                case 0xAD:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        AC = memory.read(getOperand((int)addressingModes.absolute));
                        break;
                    }
                case 0xBD:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        PC++;
                        AC = memory.read(getOperand((int)addressingModes.absolutex));
                        break;
                    }
                case 0xB9:  // Addressing Mode : Absolute, Y
                    {       // Length Byte(s) : 3
                        PC++;
                        AC = memory.read(getOperand((int)addressingModes.absolutey));
                        break;
                    }
                case 0xA1:  // Addressing Mode : (Indirect, X)
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = memory.read(getOperand((int)addressingModes.indexedindirect));
                        break;
                    }
                case 0xB1:  // Addressing Mode : (Indirect), Y
                    {       // Length Byte(s) : 2
                        PC++;
                        AC = memory.read(getOperand((int)addressingModes.indirectindexed));
                        break;
                    }
                //
                // LDX : Load X Register
                //
                case 0xA2:  // Addressing Mode: Immediate
                    {       // Length Byte(s) : 2
                        PC++;
                        X = getOperand((int)addressingModes.immediate);
                        setZeroFlag(X);
                        setNegativeFlag(X);
                        break;
                    }
                case 0xA6:  // Addressing Mode: Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        X = memory.read(getOperand((int)addressingModes.zeropage));
                        setZeroFlag(X);
                        setNegativeFlag(X);
                        break;
                    }
                case 0xB6:  // Addressing Mode: Zero Page, Y
                    {       // Length Byte(s) : 2
                        PC++;
                        X = memory.read(getOperand((int)addressingModes.zeropagey));
                        setZeroFlag(X);
                        setNegativeFlag(X);
                        break;
                    }
                case 0xAE:  // Addressing Mode: Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        X = memory.read(getOperand((int)addressingModes.absolute));
                        setZeroFlag(X);
                        setNegativeFlag(X);
                        break;
                    }
                case 0xBE:  // Addressing Mode: Absolute, Y
                    {       // Length Byte(s) : 3
                        PC++;
                        X = memory.read(getOperand((int)addressingModes.absolutey));
                        setZeroFlag(X);
                        setNegativeFlag(X);
                        break;
                    }
                //
                // LDY : Load Y Register
                //
                case 0xA0:  // Addressing Mode: Immediate
                    {       // Length Byte(s) : 2
                        PC++;
                        Y = getOperand((int)addressingModes.immediate);
                        setZeroFlag(Y);
                        setNegativeFlag(Y);
                        break;
                    }
                case 0xA4:  // Addressing Mode: Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        Y = memory.read(getOperand((int)addressingModes.zeropage));
                        setZeroFlag(Y);
                        setNegativeFlag(Y);
                        break;
                    }
                case 0xB4:  // Addressing Mode: Zero Page, X
                    {       // Length Byte(s) : 2
                        PC++;
                        Y = memory.read(getOperand((int)addressingModes.zeropagex));
                        setZeroFlag(Y);
                        setNegativeFlag(Y);
                        break;
                    }
                case 0xAC:  // Addressing Mode: Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        Y = memory.read(getOperand((int)addressingModes.absolute));
                        setZeroFlag(Y);
                        setNegativeFlag(Y);
                        break;
                    }
                case 0xBC:  // Addressing Mode: Absolute, X
                    {       // Length Byte(s) : 3
                        PC++;
                        Y = memory.read(getOperand((int)addressingModes.absolutex));
                        setZeroFlag(Y);
                        setNegativeFlag(Y);
                        break;
                    }
                //
                // LSR : Logical Shift Right
                //
                case 0x4A:  // Addressing Mode: Accumulator
                    {       // Length Byte(s) : 1
                        break;
                    }
                case 0x46:  // Addressing Mode: Zero Page
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x56:  // Addressing Mode: Zero Page, X
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x4E:  // Addressing Mode: Absolute
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x5E:  // Addressing Mode: Absolute, X
                    {       // Length Byte(s) : 3
                        break;
                    }
                //
                // NOP : No Operation
                //
                case 0xEA:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        PC++;
                        break;
                    }
                //
                // ORA : Logical Inclusive Or
                //
                case 0x09:  // Addressing Mode : Immediate
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x05:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x15:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x0D:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x1D:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x19:  // Addressing Mode : Absolute, Y
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x01:  // Addressing Mode : (Indirect, X)
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x11:  // Addressing Mode : (Indirect), Y
                    {       // Length Byte(s) : 2
                        break;
                    }
                //
                // PHA : Push Accumulator
                //
                case 0x48:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        //PC++;
                        push((byte)AC);
                        break;
                    }
                //
                // PHP : Push Processor Status
                //
                case 0x08:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        int procStat = 0;
                        for (int i = 0; i < 8; i++)
                        {
                            if (statusRegister[i] == true)
                                procStat += (int)Math.Pow(2, i);
                        }
                        push((byte)procStat);
                        break;
                    }
                //
                // PLA : Pull Accumulator
                //
                case 0x68:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        //PC++;
                        AC = pop();
                        setNegativeFlag(AC);
                        setZeroFlag(AC);
                        break;
                    }
                //
                // PLP : Pull Processor Status
                //
                case 0x28:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        int tmpStack = pop();
                        for (int i = 0; i < 8; i++)
                        {
                            statusRegister[i] = false;
                            if ((tmpStack & (int)Math.Pow(2, i)) == (int)Math.Pow(2, i))
                                statusRegister[i] = true;
                        }
                        break;
                    }
                //
                // ROL : Rotate Left
                //
                case 0x2A:  // Addressing Mode: Accumulator
                    {       // Length Byte(s) : 1
                        break;
                    }
                case 0x26:  // Addressing Mode: Zero Page
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x36:  // Addressing Mode: Zero Page, X
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x2E:  // Addressing Mode: Absolute
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x3E:  // Addressing Mode: Absolute, X
                    {       // Length Byte(s) : 3
                        break;
                    }
                //
                // ROR : Rotate Right
                //
                case 0x6A:  // Addressing Mode: Accumulator
                    {       // Length Byte(s) : 1
                        break;
                    }
                case 0x66:  // Addressing Mode: Zero Page
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x76:  // Addressing Mode: Zero Page, X
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0x6E:  // Addressing Mode: Absolute
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0x7E:  // Addressing Mode: Absolute, X
                    {       // Length Byte(s) : 3
                        break;
                    }
                //
                // RTI : Return from Interupt
                //
                case 0x40:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        break;
                    }
                //
                // RTS : Return from Subroutine
                //
                case 0x60:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
						PC = pop();
                        break;
                    }
                //
                // SBC : Subtract with Carry
                //
                case 0xE9:  // Addressing Mode : Immediate
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0xE5:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0xF5:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0xED:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0xFD:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0xF9:  // Addressing Mode : Absolute, Y
                    {       // Length Byte(s) : 3
                        break;
                    }
                case 0xE1:  // Addressing Mode : (Indirect, X)
                    {       // Length Byte(s) : 2
                        break;
                    }
                case 0xF1:  // Addressing Mode : (Indirect), Y
                    {       // Length Byte(s) : 2
                        break;
                    }
                //
                // SEC : Set Carry Flag
                //
                case 0x38:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        // PC++;
                        statusRegister[(int)statusRegisters.carry] = true;
                        break;
                    }
                //
                // SED : Set Decimal Flag
                //
                case 0xF8:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        // PC++;
                        statusRegister[(int)statusRegisters.decimalMode] = true;
                        break;
                    }
                //
                // SEI : Set Interupt Disable
                //
                case 0x78:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        // PC++;
                        statusRegister[(int)statusRegisters.interruptDisable] = true;
                        break;
                    }
                //
                // STA : Store Accumulator
                //
                case 0x85:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        memory.write(getOperand((int)addressingModes.zeropage), (byte)AC);
                        break;
                    }
                case 0x95:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        PC++;
                        memory.write(getOperand((int)addressingModes.zeropagex), (byte)AC);
                        break;
                    }
                case 0x8D:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        memory.write(getOperand((int)addressingModes.absolute), (byte)AC);
                        break;
                    }
                case 0x9D:  // Addressing Mode : Absolute, X
                    {       // Length Byte(s) : 3
                        PC++;
                        memory.write(getOperand((int)addressingModes.absolutex), (byte)AC);
                        break;
                    }
                case 0x99:  // Addressing Mode : Absolute, Y
                    {       // Length Byte(s) : 3
                        PC++;
                        memory.write(getOperand((int)addressingModes.absolutey), (byte)AC);
                        break;
                    }
                case 0x81:  // Addressing Mode : (Indirect, X)
                    {       // Length Byte(s) : 2
                        PC++;
                        memory.write(getOperand((int)addressingModes.indexedindirect), (byte)AC);
                        break;
                    }
                case 0x91:  // Addressing Mode : (Indirect), Y
                    {       // Length Byte(s) : 2
                        PC++;
                        memory.write(getOperand((int)addressingModes.indirectindexed), (byte)AC);
                        break;
                    }
                //
                // STX : Store X Register
                //
                case 0x86:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        memory.write(getOperand((int)addressingModes.zeropage), (byte)X);
                        break;
                    }
                case 0x96:  // Addressing Mode : Zero Page, Y
                    {       // Length Byte(s) : 2
                        PC++;
                        memory.write(getOperand((int)addressingModes.zeropagey), (byte)X);
                        break;
                    }
                case 0x8E:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        memory.write(getOperand((int)addressingModes.absolute), (byte)X);
                        break;
                    }
                //
                // STY : Store Y Register
                //
                case 0x84:  // Addressing Mode : Zero Page
                    {       // Length Byte(s) : 2
                        PC++;
                        memory.write(getOperand((int)addressingModes.zeropage), (byte)Y);
                        break;
                    }
                case 0x94:  // Addressing Mode : Zero Page, X
                    {       // Length Byte(s) : 2
                        PC++;
                        memory.write(getOperand((int)addressingModes.zeropagex), (byte)Y);
                        break;
                    }
                case 0x8C:  // Addressing Mode : Absolute
                    {       // Length Byte(s) : 3
                        PC++;
                        memory.write(getOperand((int)addressingModes.absolute), (byte)Y);
                        break;
                    }
                //
                // TAX : Transfer Accumulator to X
                //
                case 0xAA:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        X = AC;
                        setNegativeFlag(X);
                        setZeroFlag(X);
                        break;
                    }
                //
                // TAY : Transfer Accumulator to Y
                //
                case 0xA8:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        Y = AC;
                        setNegativeFlag(Y);
                        setZeroFlag(Y);
                        break;
                    }
                //
                // TSX : Transfer Stack Pointer to X
                //
                case 0xBA:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        X = SP;
                        setNegativeFlag(X);
                        setZeroFlag(X);
                        break;
                    }
                //
                // TXA : Transfer X to Accumulator
                //
                case 0x8A:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        AC = X;
                        setNegativeFlag(AC);
                        setZeroFlag(AC);
                        break;
                    }
                //
                // TXS : Transfer X to Stack Pointer
                //
                case 0x9A:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        SP = X;
                        break;
                    }
                //
                // TYA : Transfer Y to Accumulator
                //
                case 0x98:  // Addressing Mode: Implied
                    {       // Length Byte(s) : 1
                        AC = Y;
                        setNegativeFlag(AC);
                        setZeroFlag(AC);
                        break;
                    }
            }
            // tbHex.Text = String.Format("{0:x2}", uiDecimal);
            /*Console.WriteLine("Memory Contents: 0x"
                + String.Format("{0:x2}", (uint)System.Convert.ToUInt32(PC.ToString()))
                + " = 0x"
                + String.Format("{0:x2}", (uint)System.Convert.ToUInt32(currentOpcode.ToString()))
                + "; X = " + String.Format("{0:x2}", (uint)System.Convert.ToUInt32(X.ToString()))
                + "; Y = " + String.Format("{0:x2}", (uint)System.Convert.ToUInt32(Y.ToString()))
                + "; AC = " + String.Format("{0:x2}", (uint)System.Convert.ToUInt32(AC.ToString()))
                + "; PC = " + String.Format("{0:x2}", (uint)System.Convert.ToUInt32(PC.ToString()))
                );*/
        }

        private int getOperand(int addressingMode)
        {
            int value = 0;
            int address = 0;

            switch (addressingMode)
            {
                case (int)addressingModes.imp:
                    {
                        break;
                    }
                case (int)addressingModes.accumulator:
                    {
                        break;
                    }
                case (int)addressingModes.immediate:
                    {
                        value = memory.read(PC);
                        break;
                    }
                case (int)addressingModes.zeropage:
                    {
                        address = memory.read(PC);
                        value = memory.read(address);
                        break;
                    }
                case (int)addressingModes.zeropagex:
                    {
                        address = memory.read(PC);
                        value = memory.read((address + X) & 0xFF);
                        break;
                    }
                case (int)addressingModes.zeropagey:
                    {
                        address = memory.read(PC);
                        value = memory.read((address + Y) & 0xFF);
                        break;
                    }
                case (int)addressingModes.relative:
                    {
                        address = memory.read(PC);
                        value = memory.read(PC + address);
                        break;
                    }
                case (int)addressingModes.absolute:
                    {
                        address = memory.read(PC);
                        PC++;
                        address += 256 * memory.read(PC);
                        //value = (value * 256) + memory.read(PC);
                        //value = memory.read(address);
                        value = address;
                        break;
                    }
                case (int)addressingModes.absolutex:
                    {
                        address = memory.read(PC);
                        PC++;
                        address += 256 * memory.read(PC);
                        //address = (address * 256) + memory.read(PC);
                        value = memory.read(address + X);
                        break;
                    }
                case (int)addressingModes.absolutey:
                    {
                        address = memory.read(PC);
                        PC++;
                        address += 256 * memory.read(PC);
                        //address = (address * 256) + memory.read(PC);
                        value = memory.read(address + Y);
                        break;
                    }
                case (int)addressingModes.indirect:
                    {
                        address = memory.read(PC);
                        //PC++;
                        //address += 256 * memory.read(PC);
                        // needs changing?
                        value = memory.read(address) + 256 * memory.read(address + 1);
                        break;
                    }
                case (int)addressingModes.indexedindirect: // (Indirect, X)
                    {
                        address = memory.read(PC);
                        //PC++;
                        //address += 256 * memory.read(PC);
                        address = memory.read(address + X);
                        value = memory.read(address) & 0xFFFF;
                        break;
                    }
                case (int)addressingModes.indirectindexed: // (Indirect), Y
                    {
                        address = memory.read(PC);
                        address = memory.read(address + Y);
                        address = memory.read(address);
                        value = memory.read(address) & 0xFFFF;
                        break;
                    }
            }

            return value;
        }

        private void setZeroFlag(int value)
        {
            if(value == 0)
                statusRegister[(int)statusRegisters.zero] = true;
            else
                statusRegister[(int)statusRegisters.zero] = false;
        }

        private void setNegativeFlag(int value)
        {
            if (value > 127)
                statusRegister[(int)statusRegisters.negative] = true;
            else
                statusRegister[(int)statusRegisters.negative] = false;
        }

        private void setCarryFlag(int value)
        {
            if (value > 255)
                statusRegister[(int)statusRegisters.carry] = true;
            else
                statusRegister[(int)statusRegisters.carry] = false;
        }

        private void setOverflowFlag(int value)
        {
            if (value > 255)
                statusRegister[(int)statusRegisters.overflow] = true;
            else
                statusRegister[(int)statusRegisters.overflow] = false;
        }
    }
}
